﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Axial
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
	public class CallHandlerAttribute : Attribute
	{
		public Type Type { get; private set; }

		public CallHandlerAttribute(Type type)
		{
			Type = type;
		}
	}

	public static partial class CallHandler
	{
		static Dictionary<Type, MethodInfo> _handlers = new Dictionary<Type, MethodInfo>();
		static CallHandler()
		{
			// go through private static methods to find handlers
			MethodInfo[] possibleHandlers = typeof(CallHandler).GetMethods(BindingFlags.Static | BindingFlags.NonPublic);
			foreach (var possibleHandler in possibleHandlers)
			{
				CallHandlerAttribute attribute = possibleHandler.GetCustomAttributes(typeof(CallHandlerAttribute), false).Cast<CallHandlerAttribute>().FirstOrDefault();
				if (attribute == null)
					continue;
				if (_handlers.Keys.Contains(attribute.Type))
					continue;
				if (possibleHandler.ReturnType != typeof(string))
					continue;
				ParameterInfo[] param = possibleHandler.GetParameters();
				if (param.Length != 3 || param[0].ParameterType != typeof(MethodInfo) || 
					param[1].ParameterType != typeof(object) || param[2].ParameterType != typeof(object[]))
					continue;
				_handlers[attribute.Type] = possibleHandler;
			}
		}

		public static bool IsMethodHandled(MethodInfo method)
		{
			if (typeof(System.Web.UI.Page).IsAssignableFrom(method.DeclaringType))
				return true;
			if (_handlers.Keys.Contains(method.DeclaringType))
				return true;
			if (method.DeclaringType.IsGenericType && _handlers.Keys.Contains(method.DeclaringType.GetGenericTypeDefinition()))
				return true;
			// hard code object.ToString() until virtual methods are handled properly
			// TODO: handle virtual methods properly
			if (method.GetBaseDefinition().DeclaringType == typeof(object) && method.Name == "ToString")
				return true;
			if (method.DeclaringType.BaseType == typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
				return true;
			return false;
		}

		public static string GetCode(MethodInfo method, Stack stack)
		{
			object[] pops = GetParametersFromStack(stack, method);
			object called = method.IsStatic ? null : stack.Pop();

			if (typeof(System.Web.UI.Page).IsAssignableFrom(method.DeclaringType))
				return "window." + method.Name + "(" + string.Join(", ", pops.Cast<string>().ToArray()) + ")";
			// hard code object.ToString() until virtual methods are handled properly
			if (method.GetBaseDefinition().DeclaringType == typeof(object) && method.Name == "ToString")
				return (string)_handlers[typeof(object)].Invoke(null, new object[] { method, called, pops });

			if (_handlers.Keys.Contains(method.DeclaringType))
				return (string)_handlers[method.DeclaringType].Invoke(null, new object[] { method, called, pops });
			if (method.DeclaringType.IsGenericType)
				return (string)_handlers[method.DeclaringType.GetGenericTypeDefinition()].Invoke(null, new object[] { method, called, pops });
			if (method.DeclaringType.BaseType == typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
				return (string)_handlers[typeof(System.Web.Services.Protocols.SoapHttpClientProtocol)].Invoke(null, new object[] { method, called, pops });
			throw new NotImplementedException(method.Name + " on " + method.DeclaringType.Name + " is not supported");
		}

		private static object[] GetParametersFromStack(Stack stack, MethodInfo method)
		{
			ArrayList pops = new ArrayList();
			foreach (var param in Enumerable.Range(0, method.GetParameters().Count()))
				pops.Add(stack.Pop());
			pops.Reverse();
			return pops.ToArray();
		}

		private static string StandardHandler(MethodInfo method, object called, object[] stack)
		{
			string propertyName = method.Name.Length > 4 ? method.Name.Substring(4).LowerFirstLetter() : string.Empty;

			if (method.Name == "op_Explicit")
				return stack[0].ToString();
			if (method.Name == "get_Item")
				return string.Format("{0}[{1}]", called, stack[0]);
			if (method.Name == "set_Item")
				return string.Format("{0}[{1}] = {2}", called, stack[0], stack[1]);
			if (method.Name.StartsWith("get_"))
				return string.Format("{0}.{1}", called, propertyName);
			if (method.Name.StartsWith("set_"))
				return string.Format("{0}.{1} = {2}", called, propertyName, stack[0]);

			string function = method.Name.LowerFirstLetter();
			if (called == null)
				called = method.DeclaringType.Name;
			switch (stack.Length)
			{
				case 0: return string.Format("{0}.{1}()", called, function);
				case 1: return string.Format("{0}.{1}({2})", called, function, stack[0]);
				case 2: return string.Format("{0}.{1}({2}, {3})", called, function, stack[0], stack[1]);
				case 3: return string.Format("{0}.{1}({2}, {3}, {4})", called, function, stack[0], stack[1], stack[2]);
				case 4: return string.Format("{0}.{1}({2}, {3}, {4}, {5})", called, function, stack[0], stack[1], stack[2], stack[3]);
				case 5: return string.Format("{0}.{1}({2}, {3}, {4}, {5}, {6})", called, function, stack[0], stack[1], stack[2], stack[3], stack[4]);
				case 6: return string.Format("{0}.{1}({2}, {3}, {4}, {5}, {6}, {7})", called, function, stack[0], stack[1], stack[2], stack[3], stack[4], stack[5]);
			}
			throw new ArgumentException("stack too big for StaticFunctionCall", "stack");
		}
	}
}
